package cn.texous.util.commons.util;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * http 请求封装
 */
@Slf4j
public class MessageUtil {


    /**
     * 发送请求
     *
     * @param requestUrl    请求链接
     * @param requestMethod 请求方法
     * @param outputStr     输出流写数据
     */
    public static String sendHttpsRequest(String requestUrl,
                                          String requestMethod,
                                          String outputStr) {
        return sendHttpsRequest(requestUrl, requestMethod, outputStr, null, null, null);
    }

    /**
     * 发送 带证书的请求
     *
     * @param requestUrl    requestUrl
     * @param requestMethod requestMethod
     * @param outputStr     outputStr
     * @param certPath      certPath
     * @param certPwd       certPwd
     * @return return
     */
    public static String sendHttpsRequest(String requestUrl,
                                          String requestMethod,
                                          String outputStr,
                                          String certPath,
                                          String certPwd) {
        return sendHttpsRequest(requestUrl, requestMethod, outputStr, null, certPath, certPwd);
    }

    /**
     * 发送https请求
     *
     * @param requestUrl    请求链接
     * @param requestMethod 请求方法
     * @param outputStr     输出流写数据
     * @param contentType   传输内容类型
     */
    public static String sendHttpsRequest(String requestUrl,
                                          String requestMethod,
                                          String outputStr,
                                          String contentType,
                                          String certPath,
                                          String certPwd) {
        HttpsRequestParam param = new MessageUtil.HttpsRequestParam();
        param.setCertPath(certPath);
        param.setCertPwd(certPwd);
        param.setOutputStr(outputStr);
        param.setRequestMethod(requestMethod);
        param.setRequestUrl(requestUrl);

        Map<String, String> header = new HashMap<>();
        header.put("Content-Type", contentType);
        param.setHeader(header);

        return sendHttpsRequest(param);
    }

    /**
     * 发送 https 请求
     *
     * @param param 参数
     * @return
     */
    public static String sendHttpsRequest(HttpsRequestParam param) {
        if (!isHttpsRequest(param.getRequestUrl())) {
            log.info("请使用正确的请求方式：{}", param.getRequestUrl());
            return null;
        }
        return commonRequest(param);
    }

    /**
     * 发送 HTTP 请求
     *
     * @param param 参数
     * @return
     */
    public static String sendHttpRequest(HttpRequestParam param) {
        if (!isHttpRequest(param.getRequestUrl())) {
            log.info("请使用正确的请求方式：{}", param.getRequestUrl());
            return null;
        }
        return commonRequest(param);
    }

    /**
     * 通用请求
     *
     * @param param 参数
     * @return
     */
    public static String commonRequest(RequestParam param) {
        HttpURLConnection conn = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            if (isHttpsRequest(param.getRequestUrl()))
                conn = getHttpsConnection(param);
            else
                conn = getHttpConnection(param);

            conn.setConnectTimeout(5000);
            conn.setReadTimeout(10000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式（GET/POST）
            conn.setRequestMethod(param.getRequestMethod());
            if (param.getHeader() != null
                    && !param.getHeader().isEmpty()) {
                Set<Map.Entry<String, String>> entrySet = param.getHeader().entrySet();
                for (Map.Entry<String, String> entry : entrySet) {
                    if (entry.getKey() != null
                            && entry.getValue() != null)
                        conn.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            // 当outputStr不为null时向输出流写数据
            if (null != param.getOutputStr()) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(param.getOutputStr().getBytes(param.getOutCharset()));
                outputStream.close();
            }
            if (conn.getResponseCode() >= 400)
                inputStream = conn.getErrorStream();
            else
                inputStream = conn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, param.getInCharset());
            bufferedReader = new BufferedReader(inputStreamReader);
            String str;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            return buffer.toString();
        } catch (Exception e) {
            log.error("http请求异常：{" + param.getRequestUrl() + "}", e);
        } finally {
            if (bufferedReader != null)
                try {
                    bufferedReader.close();
                } catch (Exception e) {
                    log.error("bufferedReader close error", e);
                }
            if (inputStreamReader != null)
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    log.error("inputStreamReader close error", e);
                }
            if (inputStream != null)
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("inputStream close error", e);
                }
            if (conn != null)
                conn.disconnect();
        }
        return null;
    }

    /**
     * 获取证书配置
     *
     * @param path 证书路径
     * @param pwd  密码
     * @return return
     * @throws Exception exception
     */
    public static KeyManager[] getKeyManager(String path, String pwd) throws Exception {
        //导入客户端证书
        KeyStore ks = KeyStore.getInstance("pkcs12");
        FileInputStream instream = new FileInputStream(new File(path));
        ks.load(instream, pwd.toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, pwd.toCharArray());
        return kmf.getKeyManagers();
    }

    /**
     * 判断是否为 https 请求
     *
     * @param url 请求路径
     * @return
     */
    public static boolean isHttpsRequest(String url) {
        if (url == null)
            return false;
        return url.startsWith("https");
    }

    /**
     * 判断是否为 http 请求
     *
     * @param url 请求路径
     * @return
     */
    public static boolean isHttpRequest(String url) {
        if (url == null)
            return false;
        return url.startsWith("http");
    }

    private static HttpURLConnection getHttpConnection(RequestParam param) throws IOException {
        URL url = new URL(param.getRequestUrl());
        return (HttpURLConnection) url.openConnection();
    }

    /**
     * 获取 https 请求 HttpURLConnection
     *
     * @param param 参数
     * @return return
     * @throws Exception exception
     */
    private static HttpURLConnection getHttpsConnection(RequestParam param) throws Exception {
        TrustManager[] tm = {new MyX509TrustManager()};
        SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
        KeyManager[] keyManagers = null;
        if (param.getCertPath() != null)
            keyManagers = getKeyManager(param.getCertPath(), param.getCertPwd());
        sslContext.init(keyManagers, tm, new SecureRandom());
        // 从上述SSLContext对象中得到SSLSocketFactory对象
        SSLSocketFactory ssf = sslContext.getSocketFactory();
        URL url = new URL(param.getRequestUrl());
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setSSLSocketFactory(ssf);
        return conn;
    }

    /***/
    @Data
    @NoArgsConstructor
    public static class HttpsRequestParam extends BaseRequestParam implements RequestParam {
        private String certPath;
        private String certPwd;

        @Builder
        public HttpsRequestParam(String requestUrl,
                                 String requestMethod,
                                 String outputStr,
                                 Map<String, String> header,
                                 String outCharset,
                                 String inCharset,
                                 String certPath,
                                 String certPwd) {
            super(requestUrl, requestMethod, outputStr, header, outCharset, inCharset);
            this.certPath = certPath;
            this.certPwd = certPwd;
        }

    }

    /***/
    @Data
    @NoArgsConstructor
    public static class HttpRequestParam extends BaseRequestParam implements RequestParam {

        @Builder
        public HttpRequestParam(String requestUrl,
                                String requestMethod,
                                String outputStr,
                                Map<String, String> header,
                                String outCharset,
                                String inCharset) {
            super(requestUrl, requestMethod, outputStr, header, outCharset, inCharset);
        }

        @Override
        public String getCertPath() {
            return null;
        }

        @Override
        public String getCertPwd() {
            return null;
        }
    }

    /***/
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class BaseRequestParam {
        private String requestUrl;
        private String requestMethod;
        private String outputStr;
        private Map<String, String> header;
        private String outCharset;
        private String inCharset;

        public String getOutCharset() {
            return outCharset == null ? "UTF-8" : outCharset;
        }

        public String getInCharset() {
            return inCharset == null ? "UTF-8" : inCharset;
        }
    }

    /***/
    public interface RequestParam {
        String getRequestUrl();

        String getRequestMethod();

        String getOutputStr();

        Map<String, String> getHeader();

        String getCertPath();

        String getCertPwd();

        String getOutCharset();

        String getInCharset();

    }

}
